﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

namespace GBlason.Common.CustomCommand
{
    /// <summary>
    /// Handle the list of command (for a given object / target) and the tools related
    /// </summary>
    public class CommandRepository
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CommandRepository"/> class.
        /// Instanciate an empty command list
        /// </summary>
        public CommandRepository()
        {
            CommandHistory = new ObservableCollection<CommandGeneric>();
            CommandHistory.CollectionChanged += CommandHistoryCollectionChanged;
            LastSavedCommand = null;
        }

        /// <summary>
        /// Used when an event related to the commands occur.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CommandEvent;

        /// <summary>
        /// Raises the <see cref="CommandEvent"/> event.
        /// Used to update the GUI by unvalidating the Viewmodels properties (mostly for saving information purpose)
        /// </summary>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        public void OnCommandEvent(NotifyCollectionChangedEventArgs e)
        {
            var handler = CommandEvent;
            if (handler != null) handler(this, e);
        }

        /// <summary>
        /// Called when The commands in the list are changed (new or delete). Update the GUI by firing the OnCommandEvent.
        /// Link all the new item to the SubCommandEvent delegate, to know when they are themselves changed
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        void CommandHistoryCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            OnCommandEvent(e);
            if (e.NewItems != null)
            {
                foreach (var comm in e.NewItems.OfType<CommandGeneric>())
                {
                    comm.CommandExecutionStatusChanged += SubCommandEvent;
                }
            }
            if (e.OldItems == null) return;
            foreach (var comm in e.OldItems.OfType<CommandGeneric>())
            {
                comm.CommandExecutionStatusChanged -= SubCommandEvent;
            }
        }

        /// <summary>
        /// Delegate used when the ExecutionStatusChanged Event is fired by one of the commands in the list of commands.
        /// Allow the update of the GUI to know the saving state of a current command list
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void SubCommandEvent(object sender, EventArgs e)
        {
            OnCommandEvent(null);
        }

        /// <summary>
        /// Gets the command history.
        /// </summary>
        public ObservableCollection<CommandGeneric> CommandHistory { get; private set; }

        /// <summary>
        /// Gets or sets the last saved command.
        /// Used to know if the file have to be saved or if it is already the case.
        /// Null when no commands or no save (initialisation) have to be set when a saving is done
        /// </summary>
        /// <value>
        /// The last saved command.
        /// </value>
        public CommandGeneric LastSavedCommand { get; set; }

        /// <summary>
        /// Gets a value indicating whether this instance has undoable command.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has undoable command; otherwise, <c>false</c>.
        /// </value>
        public  bool HasUndoableCommand
        {
            get { return CommandHistory.Any(cm => cm.Done); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has redoable command.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has redoable command; otherwise, <c>false</c>.
        /// </value>
        public bool HasRedoableCommand
        {
            get { return CommandHistory.Any(cm => !cm.Done); }
        }

        /// <summary>
        /// Gets the last undoable command.
        /// </summary>
        public CommandGeneric LastUndoableCommand
        {
            get { return CommandHistory.LastOrDefault(cm => cm.Done); }
        }

        /// <summary>
        /// Gets the last redoable command.
        /// </summary>
        public CommandGeneric FirstRedoableCommand
        {
            get { return CommandHistory.FirstOrDefault(cm => !cm.Done); }
        }

        /// <summary>
        /// Undoes the commands until the target to undo (from the last action).
        /// </summary>
        /// <param name="targetToUndo">The target to undo.</param>
        public void UndoCommandsUntil(CommandGeneric targetToUndo)
        {
            foreach (var com in CommandHistory.Reverse())
            {
                if(com.Done)
                    com.Undo();   
                if (com == targetToUndo)
                {
                    break;
                }
            }
        }

        public void RedoCommandsUntil(CommandGeneric targetToRedo)
        {
            foreach (var com in CommandHistory)
            {
                if(!com.Done)
                    com.Redo();
                if (com == targetToRedo)
                {
                    break;
                }
            }
        }

        public void AddCommandHistory(CommandGeneric changeShapeCommand)
        {
            var counter = 0;
            while(counter<CommandHistory.Count)
            {
                if(!CommandHistory[counter].Done)
                    CommandHistory.RemoveAt(counter);
                else
                {
                    counter++;
                }
            }
            CommandHistory.Add(changeShapeCommand);
        }
    }
}
